﻿using System;
using System.Collections.Generic;
using Qadir.Collections.Generic;
using System.IO;

namespace Qadir.IO
{
    /// <summary>
    /// A class allowing for simultaneous actions on multiple streams.
    /// </summary>
    public class Streamer : Manager<Stream>, IDisposable
    {
        public Streamer()
            :base()
        {

        }

        public Streamer(IEnumerable<Stream> collection)
            :base(collection)
        {
        }

        /// <summary>
        /// Attempts to copy all streams in this instance to another Streamer. Consider just using streamer2 = new Streamer(streamer1);.
        /// </summary>
        /// <param name="destination">The destination Streamer to write to.</param>
        /// <exception cref="Qadir.IO.Streamer.StreamerException">Throws a StreamerException if the two Streamer objects do not contain an equal amount of streams.</exception>
        public void CopyTo(Streamer destination)
        {
            while (destination.Count < Count)
            {
                destination.Add(new MemoryStream());
            }
            if (Count == destination.Count)
            {
                for (int i = 0; i < Count; i++)
                {
                    destination[i] = this[i];
                }
            }
            else
                throw new StreamerException("The provided Streamer objects do not contain an equal amount of streams.");
        }

        /// <summary>
        /// Creates a System.IO.StreamReader for each stream.
        /// </summary>
        /// <returns>Returns a set of instantiated System.IO.StreamReader objects.</returns>
        public Manager<StreamReader> GetReaders()
        {
            return ApplyActionToAllAndReceiveResults<StreamReader>((stream) => { return new StreamReader(stream); });
        }

        /// <summary>
        /// Creates a System.IO.StreamWriter for each stream.
        /// </summary>
        /// <returns>Returns a set of instantiated System.IO.StreamWriter objects.</returns>
        public Manager<StreamWriter> GetWriters()
        {
            return ApplyActionToAllAndReceiveResults<StreamWriter>((stream) => { return new StreamWriter(stream); });
        }

        /// <summary>
        /// Attempts to read data from multiple buffers.
        /// </summary>
        /// <param name="buffer">A List of byte arrays to read into.</param>
        /// <param name="offset">The offset at which to read bytes.</param>
        /// <param name="count">The amount of bytes to read.</param>
        /// <returns>Returns a new Manager(int) containing result codes for each operation.</returns>
        public Manager<int> Read(List<byte[]> buffer, int offset, int count)
        {
            int index = 0;
            return ApplyActionToAllAndReceiveResults<int>((stream) =>
                {
                    int result = stream.Read(buffer[index], offset, count);
                    index++;
                    return result;
                });
        }

        /// <summary>
        /// Reads a line from each stream.
        /// </summary>
        /// <returns>Returns a read line from each stream.</returns>
        public Manager<string> ReadLine()
        {
            return ApplyActionToAllAndReceiveResults<string>((stream) =>
            {
                using (StreamReader sr = new StreamReader(stream))
                {
                   return sr.ReadLine();
                }
            });
        }

        /// <summary>
        /// Reads each stream to end.
        /// </summary>
        /// <returns>Returns a the read line from each stream.</returns>
        public Manager<string> ReadToEnd()
        {
            return ApplyActionToAllAndReceiveResults<string>((stream) =>
            {
                using (StreamReader sr = new StreamReader(stream))
                {
                    return sr.ReadToEnd();
                }
            });
        }

        /// <summary>
        /// Writes data to all streams.
        /// </summary>
        /// <param name="buffer">Binary data to be written to the stream.</param>
        /// <param name="offset">A numerical offset at which to write data.</param>
        /// <param name="count">The number of bytes to write.</param>
        public void Write(byte[] buffer, int offset, int count)
        {
            ApplyActionToAll((stream) => stream.Write(buffer, offset, count));
        }

        /// <summary>
        /// Writes a string to all streams.
        /// </summary>
        /// <param name="line">The string to be written.</param>
        public void WriteLine(string line)
        {
            byte[] buffer = System.Text.Encoding.ASCII.GetBytes(line + "\n");
            Write(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Disposes all streams within the Streamer.
        /// </summary>
        public void Dispose()
        {
            ApplyActionToAll(stream => stream.Dispose());
        }
    }

    /// <summary>
    /// An exception that occurs when dealing with a Qadir.IO.Streamer.
    /// </summary>
    [Serializable]
    public class StreamerException : Exception
    {
        public StreamerException() { }
        public StreamerException(string message) : base(message) { }
        public StreamerException(string message, Exception inner) : base(message, inner) { }
        protected StreamerException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}
